#include "stdlib.hpp"
#include <purewell/socketaddress.hpp>

namespace purewell
{

#define _xchg_ip4(x,y) struct sockaddr_in* y((struct sockaddr_in*)x)
#define _xchg_ip6(x,y) struct sockaddr_in6* y((struct sockaddr_in6*)x)
#define _xchg_cip4(x,y) const struct sockaddr_in* \
	y((const struct sockaddr_in*)x)
#define _xchg_cip6(x,y) const struct sockaddr_in6* \
	y((const struct sockaddr_in6*)x)

inline
static
bool
__setIP(struct sockaddr_in& sa, const char* host, const char* service)
{
	PWSocketAddressList lst;
	PWSocketAddress::parseName(lst, host, service,
		AF_INET, SOCK_STREAM, 0);

	if ( lst.empty() )
	{
		return false;
	}

	sa.sin_family = AF_INET;

	struct sockaddr_in& sa2(*(struct sockaddr_in*)
		lst.front().getAddress());

	if ( host )
	{
		sa.sin_addr = sa2.sin_addr;
	}

	if ( service )
	{
		sa.sin_port = sa2.sin_port;
	}

	return true;
}

inline
static
bool
__setIP(struct sockaddr_in6& sa, const char* host, const char* service)
{
	PWSocketAddressList lst;
	PWSocketAddress::parseName(lst, host, service,
		AF_INET6, SOCK_STREAM, 0);

	if ( lst.empty() )
	{
		return false;
	}

	sa.sin6_family = AF_INET6;

	struct sockaddr_in6& sa2(*(struct sockaddr_in6*)
		lst.front().getAddress());

	if ( host )
	{
		sa.sin6_addr = sa2.sin6_addr;
	}

	if ( service )
	{
		sa.sin6_port = sa2.sin6_port;
	}

	return true;
}

PWSocketAddress::PWSocketAddress()
{
	clear();
}

PWSocketAddress::PWSocketAddress(const void* sa, size_t slen)
{
	clear();
	m_slen = std::min(slen,sizeof(m_sa));
	memcpy(m_sa, sa, m_slen);
}

PWSocketAddress::PWSocketAddress(const PWSocketAddress& addr)
{
	clear();
	memcpy(m_sa, addr.m_sa, addr.m_slen);
	m_slen = addr.m_slen;
}

PWSocketAddress::~PWSocketAddress()
{
	clear();
}

void
PWSocketAddress::assign(const void* sa, size_t slen)
{
	clear();
	m_slen = std::min(slen,sizeof(m_sa));
	memcpy(m_sa, sa, m_slen);
}

size_t
PWSocketAddress::getSize(void) const
{
	return m_slen;
}

size_t
PWSocketAddress::getMaxSize(void) const
{
	return sizeof(m_sa);
}

void
PWSocketAddress::clear(void)
{
	memset(&m_sa, 0x00, sizeof(m_sa));
	m_slen = 0;
}

const void*
PWSocketAddress::getAddress(void) const
{
	return (const struct sockaddr*)&m_sa;
}

void*
PWSocketAddress::getAddress(void)
{
	return (struct sockaddr*)&m_sa;
}

PWSocketAddress&
PWSocketAddress::operator = (const PWSocketAddress& addr)
{
	memcpy(&m_sa, addr.m_sa, sizeof(m_sa));
	m_slen = addr.m_slen;
	return *this;
}

bool
PWSocketAddress::getName(char* host, size_t hlen,
	char* service, size_t slen, int flag, int* err) const
{
	const int res( getnameinfo((struct sockaddr*)&m_sa, m_slen,
		host, hlen, service, slen, flag) );
	if ( err ) *err = res;
	return ( 0 == res );
}

bool
PWSocketAddress::getName(char* host, size_t hlen,
	char* service, size_t slen, int* err) const
{
	const int flag(NI_NUMERICHOST|NI_NUMERICSERV);

	const int res( getnameinfo((struct sockaddr*)&m_sa, m_slen,
		host, hlen, service, slen, flag) );

	if ( err ) *err = res;
	return ( 0 == res );
}

PWSocketAddressList&
PWSocketAddress::parseName(PWSocketAddressList& lst,
	const char* host, const char* service,
	int family, int socktype, int protocol)
{
	lst.clear();
	PWSocketAddressList tmpList;

	struct addrinfo hints, *res, *ressave;
	memset(&hints, 0x00, sizeof(hints));
	hints.ai_family = family;
	hints.ai_socktype = socktype;
	hints.ai_protocol = protocol;

	if ( 0 != getaddrinfo(host, service, &hints, &res) )
	{
		return lst;
	}

	ressave = res;
	while ( res )
	{
		PWSocketAddress sa(res->ai_addr, res->ai_addrlen);
		tmpList.push_back(sa);
		res = res->ai_next;
	}

	freeaddrinfo(ressave);

	tmpList.swap(lst);

	return lst;
}

int
PWSocketAddress::getFamily(void) const
{
	struct sockaddr* psa((struct sockaddr*)m_sa);
	return psa->sa_family;
}

void
PWSocketAddress::setFamily(int family)
{
	struct sockaddr* psa((struct sockaddr*)m_sa);
	psa->sa_family = family;
}

bool
PWSocketAddress::setIP(int family, const char* host, const char* service)
{
	if ( family == AF_INET )
	{
		m_slen = sizeof(struct sockaddr_in);
		return __setIP(*(struct sockaddr_in*)m_sa, host, service);
	}

	if ( family == AF_INET6 )
	{
		m_slen = sizeof(struct sockaddr_in6);
		return __setIP(*(struct sockaddr_in6*)m_sa, host, service);
	}

	return false;
}

bool
PWSocketAddress::getIP(int* pfamily, char* host, char* service) const
{
	const struct sockaddr& sa(*(const struct sockaddr*)m_sa);
	const int family(sa.sa_family);

	if ( AF_INET != family && AF_INET6 != family )
	{
		return false;
	}

	if ( pfamily )
	{
		*pfamily = family;
	}

	const int flag(NI_NUMERICHOST|NI_NUMERICSERV);

	size_t hlen(64);
	size_t slen(8);

	return 0 == getnameinfo((struct sockaddr*)&m_sa, m_slen,
		host, hlen, service, slen, flag);
}

};//namespace purewell

